using System.Collections.Generic;

///<summary>
/// Mutable String class, optimized for speed and memory allocations while retrieving the final result as a string.
/// Similar use than StringBuilder, but avoid a lot of allocations done by StringBuilder (conversion of int and float to string, frequent capacity change, etc.)
/// Author: Nicolas Gadenne contact@gaddygames.com
///</summary>
public class StringFastBuilder
{
    ///<summary>Immutable string. Generated at last moment, only if needed</summary>
    private string m_stringGenerated = "";
    ///<summary>Is m_stringGenerated is up to date ?</summary>
    private bool m_isStringGenerated = false;

    ///<summary>Working mutable string</summary>
    private char[] m_buffer = null;
    private int m_bufferPos = 0;
    private int m_charsCapacity = 0;

    ///<summary>Temporary string used for the Replace method</summary>
    private List<char> m_replacement = null;

    private object m_valueControl = null;
    private int m_valueControlInt = int.MinValue;

    // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

    public StringFastBuilder(int initialCapacity = 32)
    {
        m_buffer = new char[m_charsCapacity = initialCapacity];
    }

    public bool IsEmpty()
    {
        return (m_isStringGenerated ? (m_stringGenerated == null) : (m_bufferPos == 0));
    }

    ///<summary>Return the string</summary>
    public override string ToString()
    {
        if (!m_isStringGenerated) // Regenerate the immutable string if needed
        {
            m_stringGenerated = new string(m_buffer, 0, m_bufferPos);
            m_isStringGenerated = true;
        }
        return m_stringGenerated;
    }

    // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    // Value controls methods: use a value to check if the string has to be regenerated.

    ///<summary>Return true if the valueControl has changed (and update it)</summary>
    public bool IsModified(int newControlValue)
    {
        bool changed = (newControlValue != m_valueControlInt);
        if (changed)
            m_valueControlInt = newControlValue;
        return changed;
    }

    ///<summary>Return true if the valueControl has changed (and update it)</summary>
    public bool IsModified(object newControlValue)
    {
        bool changed = !(newControlValue.Equals(m_valueControl));
        if (changed)
            m_valueControl = newControlValue;
        return changed;
    }


    // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    // Set methods: 

    ///<summary>Set a string, no memorry allocation</summary>
    public void Set(string str)
    {
        // We fill the m_chars list to manage future appends, but we also directly set the final stringGenerated
        Clear();
        Append(str);
        m_stringGenerated = str;
        m_isStringGenerated = true;
    }
    ///<summary>Caution, allocate some memory</summary>
    public void Set(object str)
    {
        Set(str.ToString());
    }

    ///<summary>Append several params: no memory allocation unless params are of object type</summary>
    public void Set<T1, T2>(T1 str1, T2 str2)
    {
        Clear();
        Append(str1); Append(str2);
    }
    public void Set<T1, T2, T3>(T1 str1, T2 str2, T3 str3)
    {
        Clear();
        Append(str1); Append(str2); Append(str3);
    }
    public void Set<T1, T2, T3, T4>(T1 str1, T2 str2, T3 str3, T4 str4)
    {
        Clear();
        Append(str1); Append(str2); Append(str3); Append(str4);
    }
    ///<summary>Allocate a little memory (20 byte)</summary>
    public void Set(params object[] str)
    {
        Clear();
        for (int i = 0; i < str.Length; i++)
            Append(str[i]);
    }

    // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    // Append methods, to build the string without allocation

    ///<summary>Reset the m_char array</summary>
    public StringFastBuilder Clear()
    {
        m_bufferPos = 0;
        m_isStringGenerated = false;
        return this;
    }

    ///<summary>Append a string without memory allocation</summary>
    public StringFastBuilder Append(string value)
    {
        ReallocateIFN(value.Length);
        int n = value.Length;
        for (int i = 0; i < n; i++)
            m_buffer[m_bufferPos + i] = value[i];
        m_bufferPos += n;
        m_isStringGenerated = false;
        return this;
    }
    ///<summary>Append an object.ToString(), allocate some memory</summary>
    public StringFastBuilder Append(object value)
    {
        Append(value.ToString());
        return this;
    }

    public StringFastBuilder Append(ulong value)
    {
        // Allocate enough memory to handle any int number
        ReallocateIFN(32);

        // Copy the digits in reverse order
        int nbChars = 0;
        do
        {
            m_buffer[m_bufferPos++] = (char)('0' + value % 10);
            value /= 10;
            nbChars++;
        } while (value != 0);

        // Reverse the result
        for (int i = nbChars / 2 - 1; i >= 0; i--)
        {
            char c = m_buffer[m_bufferPos - i - 1];
            m_buffer[m_bufferPos - i - 1] = m_buffer[m_bufferPos - nbChars + i];
            m_buffer[m_bufferPos - nbChars + i] = c;
        }
        m_isStringGenerated = false;
        return this;
    }

    ///<summary>Append an int without memory allocation</summary>
    public StringFastBuilder Append(int value)
    {
        // Allocate enough memory to handle any int number
        ReallocateIFN(16);

        // Handle the negative case
        if (value < 0)
        {
            value = -value;
            m_buffer[m_bufferPos++] = '-';
        }

        // Copy the digits in reverse order
        int nbChars = 0;
        do
        {
            m_buffer[m_bufferPos++] = (char)('0' + value % 10);
            value /= 10;
            nbChars++;
        } while (value != 0);

        // Reverse the result
        for (int i = nbChars / 2 - 1; i >= 0; i--)
        {
            char c = m_buffer[m_bufferPos - i - 1];
            m_buffer[m_bufferPos - i - 1] = m_buffer[m_bufferPos - nbChars + i];
            m_buffer[m_bufferPos - nbChars + i] = c;
        }
        m_isStringGenerated = false;
        return this;
    }

    ///<summary>Append a float without memory allocation.</summary>
    public StringFastBuilder Append(float valueF)
    {
        double value = valueF;
        m_isStringGenerated = false;
        ReallocateIFN(32); // Check we have enough buffer allocated to handle any float number

        // Handle the 0 case
        if (value == 0)
        {
            m_buffer[m_bufferPos++] = '0';
            return this;
        }

        // Handle the negative case
        if (value < 0)
        {
            value = -value;
            m_buffer[m_bufferPos++] = '-';
        }

        // Get the 7 meaningful digits as a long
        int nbDecimals = 0;
        while (value < 1000000)
        {
            value *= 10;
            nbDecimals++;
        }
        long valueLong = (long)System.Math.Round(value);

        // Parse the number in reverse order
        int nbChars = 0;
        bool isLeadingZero = true;
        while (valueLong != 0 || nbDecimals >= 0)
        {
            // We stop removing leading 0 when non-0 or decimal digit
            if (valueLong % 10 != 0 || nbDecimals <= 0)
                isLeadingZero = false;

            // Write the last digit (unless a leading zero)
            if (!isLeadingZero)
                m_buffer[m_bufferPos + (nbChars++)] = (char)('0' + valueLong % 10);

            // Add the decimal point
            if (--nbDecimals == 0 && !isLeadingZero)
                m_buffer[m_bufferPos + (nbChars++)] = '.';

            valueLong /= 10;
        }
        m_bufferPos += nbChars;

        // Reverse the result
        for (int i = nbChars / 2 - 1; i >= 0; i--)
        {
            char c = m_buffer[m_bufferPos - i - 1];
            m_buffer[m_bufferPos - i - 1] = m_buffer[m_bufferPos - nbChars + i];
            m_buffer[m_bufferPos - nbChars + i] = c;
        }

        return this;
    }

    // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

    ///<summary>Replace all occurences of a string by another one</summary>
    public StringFastBuilder Replace(string oldStr, string newStr)
    {
        if (m_bufferPos == 0)
            return this;

        if (m_replacement == null)
            m_replacement = new List<char>();

        // Create the new string into m_replacement
        for (int i = 0; i < m_bufferPos; i++)
        {
            bool isToReplace = false;
            if (m_buffer[i] == oldStr[0]) // If first character found, check for the rest of the string to replace
            {
                int k = 1;
                while (k < oldStr.Length && m_buffer[i + k] == oldStr[k])
                    k++;
                isToReplace = (k >= oldStr.Length);
            }
            if (isToReplace) // Do the replacement
            {
                i += oldStr.Length - 1;
                if (newStr != null)
                    for (int k = 0; k < newStr.Length; k++)
                        m_replacement.Add(newStr[k]);
            }
            else // No replacement, copy the old character
                m_replacement.Add(m_buffer[i]);
        }

        // Copy back the new string into m_chars
        ReallocateIFN(m_replacement.Count - m_bufferPos);
        for (int k = 0; k < m_replacement.Count; k++)
            m_buffer[k] = m_replacement[k];
        m_bufferPos = m_replacement.Count;
        m_replacement.Clear();
        m_isStringGenerated = false;
        return this;
    }

    // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

    private void ReallocateIFN(int nbCharsToAdd)
    {
        if (m_bufferPos + nbCharsToAdd > m_charsCapacity)
        {
            m_charsCapacity = System.Math.Max(m_charsCapacity + nbCharsToAdd, m_charsCapacity * 2);
            char[] newChars = new char[m_charsCapacity];
            m_buffer.CopyTo(newChars, 0);
            m_buffer = newChars;
        }
    }

}